using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace SourceFileProcessing
{
    class ARM6CPU
    {

        private UInt32[] _registers;
        private CpuMode _mode;
        private uint _cpsr;
        private ALU _alu;

        //Signalled when the file has finished being processed
        private ManualResetEvent _doneEvent = new ManualResetEvent(false);

        //Used to store variables
        Dictionary<string, UInt32> _memory = new Dictionary<string, UInt32>();

        public ARM6CPU()
        {
            //set registers etc to defaults            
            _registers = new UInt32[16];
            _mode = CpuMode.usr;
            _cpsr = 0;
            _alu = new ALU();
        }


        public void Run()
        {
            _doneEvent.Reset();
            Thread exeuctionThread = new Thread(RunEmulation);
            exeuctionThread.Start();
            _doneEvent.WaitOne();

        }

        private void RunEmulation()
        {
            try
            {
                SourceLine line = Manager.GetInstance().File.GetNextLine();
                do
                {
                    executeLine(line);
                    line = Manager.GetInstance().File.GetNextLine();
                }
                while (line.Type != SourceLineType.END);
            }
            catch (SoftwareInterruptException)
            {
                //SWI called, end loop
            }
            finally
            {
                _doneEvent.Set();
            }
        }

        private void executeLine(SourceLine line)
        {
            Console.WriteLine(line.Text);
            if (checkConditionCode(line.Code))
            {
                switch (line.Instruction)
                {
                    case ARMInstruction.ADD:
                        {
                            opAdd(line.Rd, line.Rn, line.Op1, line.SetFlags);
                            break;
                        }
                    case ARMInstruction.LDR:
                        {
                            opLdr(line.Rd, line.Op2);
                            break;
                        }
                    case ARMInstruction.STR:
                        {
                            opStr(line.Rd, line.Op2);
                            break;
                        }
                    case ARMInstruction.SWI:
                        {
                            opSwi(line.Op1);
                            break;
                        }
                    default:
                        break;
                        //Do Nothing
                }
            }
        }

        private void opAdd(int rd, int rn, UInt32 op1, bool setFlags)
        {
            //Console.WriteLine("opAdd called");
            _alu.A = _registers[op1];
            _alu.B = _registers[rn];
            _alu.Command = ALUCommand.ADD;
            _alu.Calculate();
            _registers[rd] = _alu.Result;
            if (setFlags)
            {
                CPSR = _alu.Flags;
            }
        }

        private void opLdr(int rd, string op2)
        {
           //Console.WriteLine("opLdr called");
            _registers[rd] = _memory[op2];
        }

        private void opStr(int rd, string op2)
        {
            //Console.WriteLine("opStr called");
            _memory[op2] = _registers[rd];
        }

        private void opSwi(UInt32 value)
        {
            //Console.WriteLine("opSwi called");
            throw new SoftwareInterruptException();
        }


        private bool checkConditionCode(ConditionCode conditionCode)
        {
            switch (conditionCode)
            {
                case ConditionCode.AL:
                    {
                        return true;
                    }
                default:
                    {
                        return false;
                    }
            }
        }

        public void Step()
        {
            executeLine(Manager.GetInstance().File.GetNextLine());
        }

        public uint CPSR
        {
            get
            {
                return _cpsr;
            }
            set
            {
                _cpsr = value;
            }
        }

        public void AddData(string label, uint variable)
        {
            _memory.Add(label, variable);
        }

        //This property for testing purposes only
        public Dictionary<string, UInt32> Memory
        {
            get { return _memory; }
        }

        //This property for testing purposes only
        public UInt32[] Registers
        {
            get { return _registers; }
        }
    }
        enum ARMInstruction : int
        {
            ADD,
            LDR,
            STR,
            SWI,
            NOP //For lines in the source file that are not instructions
        }
        enum ConditionCode : int
        {
            CS,
            CC,
            EQ,
            NE,
            VS,
            VC,
            GT,
            GE,
            LT,
            LE,
            MI,
            PL,
            HI,
            HS,
            LO,
            LS,
            AL
        }
        enum VariableType
        {
            DCD,
            DCW,
            DCB,
            ALIGN //Previous variable should be aligned to a word
        }
    enum SourceLineType
    {
        TTL,
        AREA,
        ENTRY,
        CODE,
        LABEL,
        VARIABLE,
        END,
        BLANK,
        COMMENT
    }
        enum CpuMode : int
        {
            fiq,
            usr,
            irq,
            svc,
            abt,
            und,
            sys

        }//end CpuMode

        //Number modes
        //# = base 10 number - decimal eg #1234 = 1234
        //0x = hex number - eg 0xA = 10
        //00 = base 10 number - decimal eg 1234 = 1234
        //&num = hex number

}